﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tetris.Grid;
using System.Reflection;

namespace Tetris.Blocks
{
    public abstract class TetrisBlock
    {
        private static int m_numSubTypes = -1;

        /// <summary>
        /// Number of elements in a TetrisBlock.
        /// </summary>
        protected const int NUM_ELEMENTS = 4;

        /// <summary>
        /// The individual blocks that make up this Tetris block.
        /// </summary>
        protected Block[] m_blocks = new Block[NUM_ELEMENTS];

        public TetrisBlock(GridPosition baseGridPosition, Texture2D texture)
        {
            InitBlockPositions(baseGridPosition, texture);
        }

        public static int NumBlockTypes()
        {
            if (m_numSubTypes == -1)
            {
                int numBlockTypes = 0;
                var subtypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(TetrisBlock)));
                foreach (var type in subtypes)
                    numBlockTypes++;

                m_numSubTypes = numBlockTypes;
            }

            return m_numSubTypes;
        }

        /// <summary>
        /// Update this tetris block; includes moving block's position and eliminating pieces of the block.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime, BlockGrid blockGrid)
        {
            Move(blockGrid, Block.MovementDirection.DOWN);
        }

        public bool CanMove(BlockGrid blockGrid, Tetris.Blocks.Block.MovementDirection direction)
        {
            foreach (Block block in m_blocks)
            {
                if (!block.CanMove(blockGrid, direction))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Set up the shape, color, and position of this tetris block.
        /// </summary>
        /// <param name="basePosition"></param>
        /// <param name="speed"></param>
        /// <param name="texture"></param>
        public abstract void InitBlockPositions(GridPosition baseGridPosition, Texture2D texture);

        /// <summary>
        /// Flip the orientation of this TetrisBlock.
        /// </summary>
        /// <param name="blockGrid"></param>
        public void FlipOrientation(BlockGrid blockGrid)
        {
            if (CanFlip(blockGrid))
            {
                // first erase all the blocks from the block grid
                foreach (Block block in m_blocks)
                    blockGrid.SetCell(block.gridPosition, null);

                GridPosition origin = CenterGridPosition();
                foreach (Block block in m_blocks)
                {
                    GridPosition flippedPosition = block.gridPosition.Rotate(true, origin);
                    // change this block's position to the flipped position
                    block.gridPosition = flippedPosition;
                    blockGrid.SetCell(block.gridPosition, block);
                }
            }
        }

        protected virtual bool CanFlip(BlockGrid blockGrid)
        {
            bool canFlip = true;

            GridPosition origin = CenterGridPosition();
            foreach (Block block in m_blocks)
            {
                GridPosition flippedPosition = block.gridPosition.Rotate(true, origin);
                if (!blockGrid.ValidGridPosition(flippedPosition) || blockGrid.ContainsStaticBlock(flippedPosition))
                {
                    canFlip = false;
                    break;
                }
            }

            return canFlip;
        }

        protected GridPosition CenterGridPosition()
        {
            Array.Sort(m_blocks, Block.CompareBlocksDownToUp);
            int centerRow = m_blocks[NUM_ELEMENTS / 2].gridPosition.row;

            Array.Sort(m_blocks, Block.CompareBlocksLeftToRight);
            int centerCol = m_blocks[NUM_ELEMENTS / 2].gridPosition.col;

            return new GridPosition(centerCol, centerRow);
        }

        /// <summary>
        /// Move this TetrisBlock in the specified direction (by one grid cell).
        /// </summary>
        /// <param name="blockGrid"></param>
        /// <param name="direction"></param>
        public void Move(BlockGrid blockGrid, Tetris.Blocks.Block.MovementDirection direction)
        {
            if (CanMove(blockGrid, direction))
            {
                // first we must sort the blocks according to the direction they're moving in
                switch (direction)
                {
                    case Block.MovementDirection.DOWN:
                        Array.Sort(m_blocks, Block.CompareBlocksDownToUp);
                        break;
                    case Block.MovementDirection.LEFT:
                        Array.Sort(m_blocks, Block.CompareBlocksLeftToRight);
                        break;
                    case Block.MovementDirection.RIGHT:
                        Array.Sort(m_blocks, Block.CompareBlocksRightToLeft);
                        break;
                }

                foreach (Block block in m_blocks)
                    block.Move(blockGrid,direction);
            }
        }

        public void MakeBlocksStatic()
        {
            foreach (Block block in m_blocks)
                block.DetachFromTetrisBlock();
        }

        public void ImmediateDrop(BlockGrid blockGrid)
        {
            while (CanMove(blockGrid, Block.MovementDirection.DOWN))
                Move(blockGrid, Block.MovementDirection.DOWN);
        }
    }
}
